/*
 ***** BEGIN LICENSE BLOCK *****
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
 * License for the specific language governing rights and limitations
 * under the License.
 *
 * The Original Code is BS1.
 *
 * The Initial Developer of the Original Code is SpaceMan.
 * Portions created by SpaceMan are Copyright (C) 2009
 * SpaceMan. All Rights Reserved.
 *
 * Contributor(s):
 * SpaceMan <sandaimespaceman@gmail.com>
 *
 * Alternatively, the contents of this file may be used under the terms
 * of the GNU Public License (the "GPL License"), in which case the
 * provisions of MPL License are applicable instead of those
 * above. If you wish to allow use of your version of this file only
 * under the terms of the GPL License and not to allow others to use
 * your version of this file under the MPL, indicate your decision by
 * deleting the provisions above and replace them with the notice and
 * other provisions required by the GPL License. If you do not delete
 * the provisions above, a recipient may use your version of this file
 * under either the MPL or the GPL License.
 ***** END LICENSE BLOCK *****
 */
package com.uniteduniverse.libs.BrainSpace1;

import java.util.Vector;

/**
 * The main BrainSpace 1 execution context. This library is completly standard-compliant.
 * @author SpaceMan
 * @version 1.0.1
 */
public class BrainSpace1
{
	/**
	 * The raw source code.
	 */
	public String rawCode;

	/**
	 * The four directions. They are used in the middle of the execution.
	 */
	public enum Direction
	{
		/**
		 * When {@link #LocationY} is less than zero(0), the program will end.
		 */
		UP,
		/**
		 * When {@link #LocationX} became higher than {@link #maxwidth}, the program will end.
		 */
		RIGHT,
		/**
		 * When {@link #LocationY} became higher than no. op lines of {@link #code}, the program will end.
		 */
		DOWN,
		/**
		 * When {@link #LocationX} is less than zero(0), the program will end.
		 */
		LEFT
	}
	private int maxwidth;
	/**
	 * The direction of the code. Not to be changed unless for debug propuse.
	 */
	public Direction direction = Direction.RIGHT;
	/**
	 * The current opcode to be executed.
	 */
	public String codechar = " ";
	/**
	 * The source code in interpreter-readable format
	 */
	public Vector<String> code = new Vector<String>();
	/**
	 * The X location of the code. Not to be changed unless for debug propuse.
	 */
	public int LocationX = 0;
	/**
	 * The Y location of the code. Not to be changed unless for debug propuse.
	 */
	public int LocationY = 0;
	/**
	 * The memory cells. Not to be changed unless for debug propuse.
	 */
	public Vector<Integer> memory = new Vector<Integer>();
	/**
	 * The current memory pointer. Not to be changed unless for debug propuse.
	 */
	public int memorypointer = 0;
	/**
	 * The input/output handler. It contains callbacks for input and output.
	 * @see BS1Handler
	 */
	public BS1Handler handler = new BS1Handler()
	{
		@Override
		public String InputCallback()
		{
			return "0";
		}

		@Override
		public void OutputCallback(int value)
		{
			System.out.print(value);
		}
	};

	/**
	 * The constructor of BrainSpace1 execution context.
	 * @param code The code to be executed.
	 * @param handler The input/output handler.
	 * @see BS1Handler
	 */
	public BrainSpace1(String code, BS1Handler handler)
	{
		this.memory.add(0);
		this.memorypointer = 0;
		this.maxwidth = 0;
		this.handler = handler;
		this.SetCode(code);
	}

	/**
	 * Rebuild the BS1 code
	 * @param code The code to be rebuilt.
	 */
	public void SetCode(String code)
	{
		this.rawCode = code;
		String[] c = code.split("\n");

		for (int i = 0; i < c.length; i ++)
		{
			if (c[i].length() > this.maxwidth)
			{
				this.maxwidth = c[i].length();
			}
			this.code.add(c[i]);
		}
	}

	/**
	 * Run the code from begin to end. If you want to debug the program, use Go() instead of Run().
	 * This method is also unsafe for infinite loops.
	 * @see #Go()
	 */
	@SuppressWarnings("empty-statement")
	public void Run()
	{
		while (Go());
	}

	/**
	 * Run for one step. This way is safer because you can prompt user
	 * to end the program in case of infinite loop.
	 * @return TRUE for continue execution or FALSE to end program.
	 * @see #Run()
	 */
	public boolean Go()
	{
		//get current opcode
		this.codechar = " ";

		char[] dummy = new char[1];
		try
		{
			dummy[0] = this.code.get(this.LocationY).charAt(this.LocationX);
		}
		catch (Exception e)
		{
			dummy[0] = ' ';
		}

		try
		{
			this.memory.get(this.memorypointer);
		}
		catch (ArrayIndexOutOfBoundsException e)
		{
			this.memory.add(this.memorypointer, 0);
		}

		this.codechar = new String(dummy);
		//System.out.println(this.codechar);
		if (this.codechar.equals("^"))
		{
			this.direction = Direction.UP;
		}
		else if (this.codechar.equalsIgnoreCase("v"))
		{
			this.direction = Direction.DOWN;
		}
		else if (this.codechar.equals("<"))
		{
			this.direction = Direction.LEFT;
		}
		else if (this.codechar.equals(">"))
		{
			this.direction = Direction.RIGHT;
		}
		else if (this.codechar.equals("\\"))
		{
			switch (this.direction)
			{
				case UP:
					this.direction = Direction.LEFT;
					break;
				case RIGHT:
					this.direction = Direction.DOWN;
					break;
				case DOWN:
					this.direction = Direction.RIGHT;
					break;
				case LEFT:
					this.direction = Direction.UP;
					break;
				default:
					this.direction = Direction.RIGHT;
					break;
			}
		}
		else if (this.codechar.equals("/"))
		{
			switch (this.direction)
			{
				case UP:
					this.direction = Direction.RIGHT;
					break;
				case RIGHT:
					this.direction = Direction.UP;
					break;
				case DOWN:
					this.direction = Direction.LEFT;
					break;
				case LEFT:
					this.direction = Direction.DOWN;
					break;
				default:
					this.direction = Direction.RIGHT;
					break;
			}
		}
		else if (this.codechar.equals("%"))
		{
			switch (this.direction)
			{
				case UP:
					this.direction = Direction.DOWN;
					break;
				case RIGHT:
					this.direction = Direction.LEFT;
					break;
				case DOWN:
					this.direction = Direction.UP;
					break;
				case LEFT:
					this.direction = Direction.RIGHT;
					break;
				default:
					this.direction = Direction.RIGHT;
					break;
			}
		}
		else if (this.codechar.equals("?"))
		{
			if (this.memory.get(this.memorypointer) != 0)
			{
				switch (this.direction)
				{
					case UP:
						this.direction = Direction.DOWN;
						break;
					case RIGHT:
						this.direction = Direction.LEFT;
						break;
					case DOWN:
						this.direction = Direction.UP;
						break;
					case LEFT:
						this.direction = Direction.RIGHT;
						break;
					default:
						this.direction = Direction.RIGHT;
						break;
				}
			}
		}
		else if (this.codechar.equals("*"))
		{
			int skip = 1;
			if (this.memory.get(this.memorypointer) != 0)
			{
				skip = 0;
			}
			switch (this.direction)
			{
				case UP:
					this.LocationY -= skip;
					break;
				case RIGHT:
					this.LocationX += skip;
					break;
				case DOWN:
					this.LocationY += skip;
					break;
				case LEFT:
					this.LocationX -= skip;
					break;
				default:
					break;
			}
		}
		else if (this.codechar.equalsIgnoreCase("x"))
		{
			return false;
		}
		else if (this.codechar.equalsIgnoreCase("l"))
		{
			if (this.memorypointer > 0)
			{
				this.memorypointer --;
			}
		}
		else if (this.codechar.equals("{"))
		{
			if (this.memorypointer > 0)
			{
				this.memorypointer --;
			}

		}
		else if (this.codechar.equalsIgnoreCase("r"))
		{
			this.memorypointer ++;
		}
		else if (this.codechar.equals("}"))
		{
			this.memorypointer ++;
		}
		else if (this.codechar.equals("+"))
		{
			this.memory.set(this.memorypointer, this.memory.get(this.memorypointer) + 1);
		}
		else if (this.codechar.equalsIgnoreCase("p"))
		{
			this.memory.set(this.memorypointer, this.memory.get(this.memorypointer) + 1);
		}
		else if (this.codechar.equals("-"))
		{
			this.memory.set(this.memorypointer, this.memory.get(this.memorypointer) - 1);
		}
		else if (this.codechar.equalsIgnoreCase("m"))
		{
			this.memory.set(this.memorypointer, this.memory.get(this.memorypointer) - 1);
		}
		else if (this.codechar.equalsIgnoreCase("o"))
		{
			this.handler.OutputCallback(this.memory.get(this.memorypointer));
		}
		else if (this.codechar.equals("0"))
		{
			this.handler.OutputCallback(this.memory.get(this.memorypointer));
		}
		else if (this.codechar.equalsIgnoreCase("i"))
		{
			String input = this.handler.InputCallback();
			if (input.length() == 0)
			{
				input = " ";
			}
			this.memory.set(this.memorypointer, input.codePointAt(0));
		}
		else if (this.codechar.equals("1"))
		{
			String input = this.handler.InputCallback();
			if (input.length() == 0)
			{
				input = " ";
			}
			this.memory.set(this.memorypointer, input.codePointAt(0));
		}
		else
		{
		}
		//move to next block
		switch (this.direction)
		{
			case UP:
				if (this.LocationY > 0)
				{
					this.LocationY --;
				}
				else
				{
					return false;
				}
				break;
			case RIGHT:
				if (this.LocationX <= width())
				{
					this.LocationX ++;
				}
				else
				{
					return false;
				}
				break;
			case DOWN:
				if (this.LocationY <= height())
				{
					this.LocationY ++;
				}
				else
				{
					return false;
				}
				break;
			case LEFT:
				if (this.LocationX > 0)
				{
					this.LocationX --;
				}
				else
				{
					return false;
				}
				break;
			default:
				//fix direction
				this.direction = Direction.RIGHT;
				break;
		} // switch (this.direction)
		return true;
	}

	/**
	 * Gets the width of the code
	 * @return The width of the code.
	 */
	public int width()
	{
		return this.maxwidth;
	}

	/**
	 * Gets the height of the code
	 * @return The height of the code.
	 */
	public int height()
	{
		return this.code.size();
	}

	/**
	 * The overall debug info.
	 * @return The debug info in string with line break.
	 */
	@Override
	public String toString()
	{
		return String.format("BrainSpace 1.0 Context(0x%s): \n X: %d, Y: %d\n Mem. Pointer: %d\n Last Opcode: %s", Integer.toHexString(this.hashCode()).toUpperCase(), this.LocationX, this.LocationY, this.memorypointer, this.codechar);
	}
}
